必要掌握!Window、WindowManager !
文章目录
一、Window和WindowManager
1.1 window
1.2 WindowManager
二、window的内部机制
2.1 window的添加
2.2 window的更新
2.3 window 删除
三、常见Window的创建过程
3.1 Activity的Window创建
3.2 Dialog的window创建
3.3 Toast的window创建
Window,表示一个窗口的抽象的概念;同时也是一个抽象类,唯一的实现是PhoneWindow。在PhoneWindow中有一个顶级View—DecorView,继承自FrameLayout,我们可以通过getDecorView()获得它,当我们调用Activity的setContentView时,其实最终会调用Window的setContentView,当我们调用Activity的findViewById时,其实最终调用的是Window的findViewById,这也间接的说明了Window是View的直接管理者。
但是Window并不是真实存在的,它更多的表示一种抽象的功能集合,View才是Android中的视图呈现形式,绘制到屏幕上的是View不是Window,但是View不能单独存在,它必需依附在Window这个抽象的概念上面,Android中需要依赖Window提供视图的有Activity,Dialog,Toast,PopupWindow,StatusBarWindow(系统状态栏),输入法窗口等,因此Activity,Dialog等视图都对应着一个Window。
创建Window,通过WindowManager即可完成。WindowManager是操作Window的入口,Window的具体实现是在WindowManagerService中。WindowManager和WindowManagerService交互是IPC(跨进程通信)过程。
Window是View的管理者,当我们说创建Window时,一方面指实例化这个管理者,一方面指 用WindowManager.addView()添加view,以view的形式来呈现Window这个概念。
一、Window和WindowManager
1.1 window
先看创建window的代码
1WindowManager windowManager = getWindowManager();
2 Button view = new Button(this);
3 view.setText("添加到window中的button");
4
5 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
6
7 layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
8 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
9 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
10 layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
11
12 layoutParams.format = PixelFormat.TRANSPARENT;
13 layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
14 layoutParams.x = 100;
15 layoutParams.y = 100;
16 layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
17 layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
18
19 windowManager.addView(view, layoutParams);
实际就只有一句windowManager.addView(view, layoutParams),这样就添加了一个Window,这个window只有一个button。看下LayoutParams的两个不太认识的属性,flags、type。
flags,决定window的显示特性,有很多值,看下常用的:
FLAG_NOT_FOCUSABLE,不需要获取焦点、不需要 输入事件,同时会自定开启FLAG_NOT_TOUCH_MODAL,最终事件会传递给下层具有焦点的window。
FLAG_NOT_TOUCH_MODAL,window区域以外的单击事件会传递给下层window,window范围内的事件自己处理。一般需要开启此标记,否则其他window不能收到事件。
FLAG_SHOW_WHEN_LOCKED,开启后 可以让window显示在锁屏的界面上。
type参数表示window的类型。window有三种类型,应用window、子window、系统window。应用window对应activity;子window要依附在父window上,如dialog;系统window需要申明权限才能创建,比如toast、系统状态栏。
window是分层的,每个window都有对应的z-ordered,层级大的在层级小的上层。应用window的层级范围是1-99,子window是1000-19999=,系统window是2000-2999,即type的值。
如果想window位于所有window顶层,那就用系统window。可以设置layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,并且,要申明使用权限,且6.0以后要让用户手动打开权限。
1<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
否则会报错:
1Caused by: android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@305c3bc -- permission denied for window type 2038
2 at android.view.ViewRootImpl.setView(ViewRootImpl.java:958)
3 at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:398)
4 at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:131)
5 at com.hfy.demo01.MainActivity.initCustomWindow(MainActivity.java:266)
6 at com.hfy.demo01.MainActivity.initView(MainActivity.java:170)
7 at com.hfy.demo01.MainActivity.onCreate(MainActivity.java:116)
8 at android.app.Activity.performCreate(Activity.java:7458)
9 at android.app.Activity.performCreate(Activity.java:7448)
10 at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1286)
11 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3409)
12 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3614)
13 at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:86)
14 at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
15 at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
16 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2199)
17 at android.os.Handler.dispatchMessage(Handler.java:112)
18 at android.os.Looper.loop(Looper.java:216)
19 at android.app.ActivityThread.main(ActivityThread.java:7625)
使用系统window的完整代码:
1 private void initCustomWindow() {
2 //6.0以上需要用户手动打开权限
3 // (SYSTEM_ALERT_WINDOW and WRITE_SETTINGS, 这两个权限比较特殊,
4 // 不能通过代码申请方式获取,必须得用户打开软件设置页手动打开,才能授权。Manifest申请该权限是无效的。)
5 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
6 if (!Settings.canDrawOverlays(this)) {
7 //打开设置页,让用户打开设置
8 Toast.makeText(this, "can not DrawOverlays", Toast.LENGTH_SHORT).show();
9 Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + MainActivity.this.getPackageName()));
10 startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
11 }else {
12 //已经打开了权限
13 handleAddWindow();
14 }
15 }else {
16 //6.0以下直接 Manifest申请该权限 就行。
17 handleAddWindow();
18 }
19 }
20
21 private void handleAddWindow() {
22 Button view = new Button(this);
23 view.setText("添加到window中的button");
24
25 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
26 WindowManager.LayoutParams.WRAP_CONTENT,
27 WindowManager.LayoutParams.WRAP_CONTENT,
28 0, 0,
29 PixelFormat.TRANSPARENT
30 );
31 // flag 设置 Window 属性
32 layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
33 // type 设置 Window 类别(层级):系统window
34 layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
35
36 layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
37 layoutParams.x = 100;
38 layoutParams.y = 100;
39
40 WindowManager windowManager = getWindowManager();
41 windowManager.addView(view, layoutParams);
42 }
43
44 @Override
45 protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
46 super.onActivityResult(requestCode, resultCode, data);
47 switch (requestCode){
48 case OVERLAY_PERMISSION_REQ_CODE:
49 if (Settings.canDrawOverlays(this)) {
50 //打开了权限
51 handleAddWindow();
52 }else {
53 Toast.makeText(this, "can not DrawOverlays", Toast.LENGTH_SHORT).show();
54 }
55 break;
56 default:
57 break;
58 }
59 }
按home键后效果:
1.2 WindowManager
WindowManager是个接口,继承自ViewManager:
1public interface ViewManager{
2 public void addView(View view, ViewGroup.LayoutParams params);
3 public void updateViewLayout(View view, ViewGroup.LayoutParams params);
4 public void removeView(View view);
5}
所以,windowManager就是 添加、更新、删除 view,实际使用的就是这三个方法,上面创建window的例子用的就是addView方法。所以,操作window就是操作view。
二、window的内部机制
window是抽象的概念,在视图中不是实际存在,它以view的形式呈现。一个window就对应一个view,window操作view实际是通过ViewRootImpl实现。使用中是通过WindowManager对的操作,无法直接访问window。下面就看看WindowManager的三个方法。
2.1 window的添加
WindowManager的实现类是WindowManagerImpl,那么看看操作view的三个方法的实现:
1@Override
2 public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
3 applyDefaultToken(params);
4 mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
5 }
6
7 @Override
8 public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
9 applyDefaultToken(params);
10 mGlobal.updateViewLayout(view, params);
11 }
12
13 @Override
14 public void removeView(View view) {
15 mGlobal.removeView(view, false);
16 }
可以看到,全都交给mGlobal处理了,那看下mGlobal,是个单例对象:
1private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
1public static WindowManagerGlobal getInstance() {
2 synchronized (WindowManagerGlobal.class) {
3 if (sDefaultWindowManager == null) {
4 sDefaultWindowManager = new WindowManagerGlobal();
5 }
6 return sDefaultWindowManager;
7 }
8 }
那么来看下mGlobal.addView,具体简要概括为3个步骤:
数据检查
更新各种参数列表
RootViewImpl添加view(含window的添加)
1public void addView(View view, ViewGroup.LayoutParams params,
2 Display display, Window parentWindow) {
3 //1、数据检查
4 if (view == null) {
5 throw new IllegalArgumentException("view must not be null");
6 }
7 if (display == null) {
8 throw new IllegalArgumentException("display must not be null");
9 }
10 if (!(params instanceof WindowManager.LayoutParams)) {
11 throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
12 }
13
14 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
15 if (parentWindow != null) {
16 parentWindow.adjustLayoutParamsForSubWindow(wparams);
17 } else {
18 // If there's no parent, then hardware acceleration for this view is
19 // set from the application's hardware acceleration setting.
20 final Context context = view.getContext();
21 if (context != null
22 && (context.getApplicationInfo().flags
23 & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
24 wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
25 }
26 }
27
28 ViewRootImpl root;
29 View panelParentView = null;
30 ...
31 //创建viewRoot(一个window对应一个viewRoot)
32 root = new ViewRootImpl(view.getContext(), display);
33 view.setLayoutParams(wparams);
34
35 //2、更新各种参数列:所有window的--view的列表、rootView的列表、view参数的列表
36 mViews.add(view);
37 mRoots.add(root);
38 mParams.add(wparams);
39
40 // do this last because it fires off messages to start doing things
41 try {
42 // 3、RootViewImpl添加view(含window的添加)
43 root.setView(view, wparams, panelParentView);
44 } catch (RuntimeException e) {
45 // BadTokenException or InvalidDisplayException, clean up.
46 if (index >= 0) {
47 removeViewLocked(index, true);
48 }
49 throw e;
50 }
51 }
52 }
接着看ViewRootImpl的setView:
1public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
2 ...
3 //1.绘制view
4 requestLayout();
5
6 if ((mWindowAttributes.inputFeatures
7 & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
8 mInputChannel = new InputChannel();
9 }
10 mForceDecorViewVisibility = (mWindowAttributes.privateFlags
11 & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
12 try {
13 mOrigWindowType = mWindowAttributes.type;
14 mAttachInfo.mRecomputeGlobalAttributes = true;
15 collectViewAttributes();
16 //2.通过session与WMS建立通信:完成window的添加
17 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
18 getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
19 mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
20 mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
21 } catch (RemoteException e) {
22 mAdded = false;
23 mView = null;
24 mAttachInfo.mRootView = null;
25 mInputChannel = null;
26 mFallbackEventHandler.setView(null);
27 unscheduleTraversals();
28 setAccessibilityFocus(null, null);
29 throw new RuntimeException("Adding window failed", e);
30 }
31 ...
32}
两个步骤:1、调用requestLayout()异步刷新view,2、mWindowSession.addToDisplay()完成window的添加。
requestLayout()内部最后走到performTraversals(),我们知道这是view绘制流程入口。如下所示:
1@Override
2 public void requestLayout() {
3 if (!mHandlingLayoutInLayoutRequest) {
4 checkThread();
5 mLayoutRequested = true;
6 scheduleTraversals();
7 }
8 }
1void scheduleTraversals() {
2 if (!mTraversalScheduled) {
3 mTraversalScheduled = true;
4 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
5 mChoreographer.postCallback(
6 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
7 if (!mUnbufferedInputDispatch) {
8 scheduleConsumeBatchedInput();
9 }
10 notifyRendererOfFramePending();
11 pokeDrawLockIfNeeded();
12 }
13 }
1final class TraversalRunnable implements Runnable {
2 @Override
3 public void run() {
4 doTraversal();
5 }
6 }
1void doTraversal() {
2 if (mTraversalScheduled) {
3 mTraversalScheduled = false;
4 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
5
6 if (mProfile) {
7 Debug.startMethodTracing("ViewAncestor");
8 }
9 // 绘制流程
10 performTraversals();
11
12 if (mProfile) {
13 Debug.stopMethodTracing();
14 mProfile = false;
15 }
16 }
17 }
至于mWindowSession.addToDisplay(),先看mWindowSession,类型是IWindowSession,是个Binder对象,具体是com.android.server.wm.Session,所以window的添加是一个IPC过程。
mWindowSessionde 是在ViewRootImpl创建时获取,由WindowManagerGlobal通过获取WindowManagerService来为 每个应用创建一个单独的session。
1public ViewRootImpl(Context context, Display display) {
2 mContext = context;
3 mWindowSession = WindowManagerGlobal.getWindowSession();
4 ...
5}
1 public static IWindowSession getWindowSession() {
2 synchronized (WindowManagerGlobal.class) {
3 if (sWindowSession == null) {
4 try {
5 InputMethodManager imm = InputMethodManager.getInstance();
6 IWindowManager windowManager = getWindowManagerService();
7 sWindowSession = windowManager.openSession(
8 new IWindowSessionCallback.Stub() {
9 @Override
10 public void onAnimatorScaleChanged(float scale) {
11 ValueAnimator.setDurationScale(scale);
12 }
13 },
14 imm.getClient(), imm.getInputContext());
15 } catch (RemoteException e) {
16 throw e.rethrowFromSystemServer();
17 }
18 }
19 return sWindowSession;
20 }
21 }
1 public static IWindowManager getWindowManagerService() {
2 synchronized (WindowManagerGlobal.class) {
3 if (sWindowManagerService == null) {
4 sWindowManagerService = IWindowManager.Stub.asInterface(
5 ServiceManager.getService("window"));
6 try {
7 if (sWindowManagerService != null) {
8 ValueAnimator.setDurationScale(
9 sWindowManagerService.getCurrentAnimatorScale());
10 }
11 } catch (RemoteException e) {
12 throw e.rethrowFromSystemServer();
13 }
14 }
15 return sWindowManagerService;
16 }
17 }
然后是WindowManagerService的openSession:
1 @Override
2 public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
3 IInputContext inputContext) {
4 if (client == null) throw new IllegalArgumentException("null client");
5 if (inputContext == null) throw new IllegalArgumentException("null inputContext");
6 Session session = new Session(this, callback, client, inputContext);
7 return session;
8 }
接着看Session的addToDisplay:
1@Override
2 public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
3 int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
4 Rect outStableInsets, Rect outOutsets,
5 DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
6 return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
7 outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
8 }
window的添加就交给WindowManagerService了。
WindowManagerService主要作用:
窗口管理:是先进行窗口的权限检查,因为系统窗口需要声明权限,然后根据相关的Display信息以及窗口信息对窗口进行校对,再然后获取对应的WindowToken,再根据不同的窗口类型检查窗口的有效性,如果上面一系列步骤都通过了,就会为该窗口创建一个WindowState对象,以维护窗口的状态和根据适当的时机调整窗口状态,最后就会通过WindowState的attach方法与SurfaceFlinger通信。因此SurfaceFlinger能使用这些Window信息来合成surfaces,并渲染输出到显示设备。
输入事件的中转站:当我们的触摸屏幕时就会产生输入事件,在Android中负责管理事件的输入是InputManagerService,它里面有一个InputManager,在启动IMS的同时会创建InputManager,在创建InputManager同时创建InputReader和InputDispatcher,InputReader会不断的从设备节点中读取输入事件,InputReader将这些原始输入事件加工后就交给InputDispatcher,而InputDispatcher它会寻找一个最合适的窗口来处理输入事件,WMS是窗口的管理者,WMS会把所有窗口的信息更新到InputDispatcher中,这样InputDispatcher就可以将输入事件派发给合适的Window,Window就会把这个输入事件传给顶级View,然后就会涉及我们熟悉的事件分发机制。
2.2 window的更新
直接看mGlobal.updateViewLayout(view, params):
1 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
2 //1、参数检查
3 if (view == null) {
4 throw new IllegalArgumentException("view must not be null");
5 }
6 if (!(params instanceof WindowManager.LayoutParams)) {
7 throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
8 }
9
10 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
11 //2、更新layoutParams及参数列表列表
12 view.setLayoutParams(wparams);
13 synchronized (mLock) {
14 int index = findViewLocked(view, true);
15 ViewRootImpl root = mRoots.get(index);
16 mParams.remove(index);
17 mParams.add(index, wparams);
18 //3、RootViewImpl更新布局
19 root.setLayoutParams(wparams, false);
20 }
21 }
再看ViewRootIml.setLayoutParams()中会调用scheduleTraversals() 重新绘制布局,其中也会调用mWindowSession.relayout来更新window ,也是IPC过程。
2.3 window 删除
直接看mGlobal.removeView(view, false):
1 public void removeView(View view, boolean immediate) {
2 if (view == null) {
3 throw new IllegalArgumentException("view must not be null");
4 }
5
6 synchronized (mLock) {
7 //找到要移除view在列表中的index
8 int index = findViewLocked(view, true);
9 View curView = mRoots.get(index).getView();
10 //移除
11 removeViewLocked(index, immediate);
12 if (curView == view) {
13 return;
14 }
15
16 throw new IllegalStateException("Calling with view " + view
17 + " but the ViewAncestor is attached to " + curView);
18 }
19 }
再看removeViewLocked(index, immediate):
1 private void removeViewLocked(int index, boolean immediate) {
2 //找到对应的ViewRoot
3 ViewRootImpl root = mRoots.get(index);
4 View view = root.getView();
5
6 if (view != null) {
7 InputMethodManager imm = InputMethodManager.getInstance();
8 if (imm != null) {
9 imm.windowDismissed(mViews.get(index).getWindowToken());
10 }
11 }
12 //ViewRoot用die来删除
13 boolean deferred = root.die(immediate);
14 if (view != null) {
15 view.assignParent(null);
16 if (deferred) {
17 //记录要删除的view
18 mDyingViews.add(view);
19 }
20 }
21 }
继续看root.die(immediate):
1 boolean die(boolean immediate) {
2 // 如果是立刻删除,直接调doDie()
3 if (immediate && !mIsInTraversal) {
4 doDie();
5 return false;
6 }
7
8 if (!mIsDrawing) {
9 destroyHardwareRenderer();
10 } else {
11 Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
12 " window=" + this + ", title=" + mWindowAttributes.getTitle());
13 }
14 //不是立刻删,就放入队列
15 mHandler.sendEmptyMessage(MSG_DIE);
16 return true;
17 }
继续看doeDie():
1 void doDie() {
2 checkThread();
3 if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
4 synchronized (this) {
5 if (mRemoved)
6 return;
7 }
8 mRemoved = true;
9 if (mAdded) {
10 //删除操作
11 dispatchDetachedFromWindow();
12 }
13
14 ...
15 //移除对应列表中的root、view、param、dyingView
16 WindowManagerGlobal.getInstance().doRemoveView(this);
17 }
看下dispatchDetachedFromWindow():
1 void dispatchDetachedFromWindow() {
2 mFirstInputStage.onDetachedFromWindow();
3 if (mView != null && mView.mAttachInfo != null) {
4 mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
5 //回调view的dispatchDetachedFromWindow方法,意思是view要从window中移除了。一般可在其中做一些资源回收工作,如 停止动画等。
6 mView.dispatchDetachedFromWindow();
7 }
8 //移除各种回调
9 mAccessibilityInteractionConnectionManager.ensureNoConnection();
10 mAccessibilityManager.removeAccessibilityStateChangeListener(
11 mAccessibilityInteractionConnectionManager);
12 mAccessibilityManager.removeHighTextContrastStateChangeListener(
13 mHighContrastTextManager);
14 removeSendWindowContentChangedCallback();
15
16 destroyHardwareRenderer();
17
18 setAccessibilityFocus(null, null);
19
20 mView.assignParent(null);
21 mView = null;
22 mAttachInfo.mRootView = null;
23
24 mSurface.release();
25
26 if (mInputQueueCallback != null && mInputQueue != null) {
27 mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
28 mInputQueue.dispose();
29 mInputQueueCallback = null;
30 mInputQueue = null;
31 }
32 if (mInputEventReceiver != null) {
33 mInputEventReceiver.dispose();
34 mInputEventReceiver = null;
35 }
36 try {
37 //删除window
38 mWindowSession.remove(mWindow);
39 } catch (RemoteException e) {
40 }
41
42 // Dispose the input channel after removing the window so the Window Manager
43 // doesn't interpret the input channel being closed as an abnormal termination.
44 if (mInputChannel != null) {
45 mInputChannel.dispose();
46 mInputChannel = null;
47 }
48
49 mDisplayManager.unregisterDisplayListener(mDisplayListener);
50
51 unscheduleTraversals();
52 }
好了,window的三个view操作就这些了。
三、常见Window的创建过程
View依附于Window这个抽象概念,有Activity、Dialog、Toast、PopupWindow等。
3.1 Activity的Window创建
Activity的启动略复杂,这里先看ActivityThread里的performLaunchActivity():
1 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
2 ...
3 //创建activity实例:通过类加载器创建
4 java.lang.ClassLoader cl = appContext.getClassLoader();
5 activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
6 ...
7 //调用Activity的attach方法--关联上下文环境变量
8 activity.attach(appContext, this, getInstrumentation(), r.token,
9 r.ident, app, r.intent, r.activityInfo, title, r.parent,
10 r.embeddedID, r.lastNonConfigurationInstances, config,
11 r.referrer, r.voiceInteractor, window, r.configCallback);
12 ...
13}
接着看activity.attach方法:
1 //实例化window,就是Window的唯一实现PhoneWindow
2 mWindow = new PhoneWindow(this, window, activityConfigCallback);
3 ...
4 //把activity作为回调接口传入window,这样window从外界接受的状态变化都会交给activity
5 //例如:dispatchTouchEvent、onAttachedToWindow、onDetachedFromWindow
6 mWindow.setCallback(this);
7 ...
8 //设置windowManager,实际就是WindowManagerImpl的实例,在activity中getWindowManager()获取的就是这个实例
9 mWindow.setWindowManager(
10 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
11 mToken, mComponent.flattenToString(),
12 (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
13 ...
14 mWindowManager = mWindow.getWindowManager();
OK,activity视图的管理者window已创建,那么什么时候用windowManager.addView() 来把activity的视图依附在window上呢?
先看Activity的setContentView方法,我们activity的视图由此方法设置:
1 public void setContentView(@LayoutRes int layoutResID) {
2 getWindow().setContentView(layoutResID);
3 initWindowDecorActionBar();
4 }
接着看PhonrWindow的setContentView:
1 public void setContentView(int layoutResID) {
2 // mContentParent为空,就调installDecor(),猜想installDecor()里面创建了mContentParent。且从名字看出mContentParent就是内容视图的容器
3 if (mContentParent == null) {
4 installDecor();
5 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
6 mContentParent.removeAllViews();
7 }
8
9 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
10 final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
11 getContext());
12 transitionTo(newScene);
13 } else {
14 //这里看到,确实把我们的视图加载到mContentParent了
15 mLayoutInflater.inflate(layoutResID, mContentParent);
16 }
17 ...
18 }
那就看installDecor():
1private void installDecor() {
2 if (mDecor == null) {
3 //创建mDecor
4 mDecor = generateDecor(-1);
5 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
6 mDecor.setIsRootNamespace(true);
7 if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
8 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
9 }
10 } else {
11 mDecor.setWindow(this);
12 }
13 if (mContentParent == null) {
14 //创建mContentParent
15 mContentParent = generateLayout(mDecor);
16 ...
17}
看下generateDecor(-1),就是new了个DecorView:
1 protected DecorView generateDecor(int featureId) {
2 Context context;
3 if (mUseDecorContext) {
4 Context applicationContext = getContext().getApplicationContext();
5 if (applicationContext == null) {
6 context = getContext();
7 } else {
8 context = new DecorContext(applicationContext, getContext());
9 if (mTheme != -1) {
10 context.setTheme(mTheme);
11 }
12 }
13 } else {
14 context = getContext();
15 }
16 return new DecorView(context, featureId, this, getAttributes());
17 }
继续看generateLayout(mDecor):
1 // Apply data from current theme.
2 TypedArray a = getWindowStyle();
3 ...
4 // 这里下面一堆代码是 根据主题,获取DecorView的布局资源
5 int layoutResource;
6 int features = getLocalFeatures();
7 if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
8 layoutResource = R.layout.screen_swipe_dismiss;
9 setCloseOnSwipeEnabled(true);
10 } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0)
11 ...
12 //把布局给到mDecor,这样mDecor就有视图了。
13 mDecor.onResourceLoaded(mLayoutInflater, layoutResource)
14 //findViewById就是getDecorView().findViewById(id);
15 //所以从DecorView中找到id为ID_ANDROID_CONTENT = com.android.internal.R.id.content 的容器,就用用来存放我们activity中设置的视图的。
16 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
17 ...
18 return contentParent;
19}
好了,通过以上流程,就清楚了activity中通过setContentView设置的布局实际是加载到DecorView的id为com.android.internal.R.id.content容器中。我们查看DecorView所有的主题的布局,发现都有这个id的容器,且是FrameLayout。
1<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2 android:orientation="vertical"
3 android:fitsSystemWindows="true">
4 <!-- Popout bar for action modes -->
5 <ViewStub android:id="@+id/action_mode_bar_stub"
6 android:inflatedId="@+id/action_mode_bar"
7 android:layout="@layout/action_mode_bar"
8 android:layout_width="match_parent"
9 android:layout_height="wrap_content"
10 android:theme="?attr/actionBarTheme" />
11
12 <FrameLayout android:id="@android:id/title_container"
13 android:layout_width="match_parent"
14 android:layout_height="?android:attr/windowTitleSize"
15 android:transitionName="android:title"
16 style="?android:attr/windowTitleBackgroundStyle">
17 </FrameLayout>
18 //这个容器
19 <FrameLayout android:id="@android:id/content"
20 android:layout_width="match_parent"
21 android:layout_height="0dip"
22 android:layout_weight="1"
23 android:foregroundGravity="fill_horizontal|top"
24 android:foreground="?android:attr/windowContentOverlay" />
25</LinearLayout>
最后一步,就是windowManager.addView了,在哪呢?
在ActivityThred的handleResumeActivity()中:
1r.activity.makeVisible();
再看activity.makeVisible():
1 void makeVisible() {
2 if (!mWindowAdded) {
3 ViewManager wm = getWindowManager();
4 //1、windowManager.addView
5 wm.addView(mDecor, getWindow().getAttributes());
6 mWindowAdded = true;
7 }
8 //2、Decor可见
9 mDecor.setVisibility(View.VISIBLE);
10 }
好了,activity的window加载过程就这样了。
3.2 Dialog的window创建
先看Dialog的构造方法:
1 Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
2 ...
3 //获取windowManager
4 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
5 //实例化PhoneWindow
6 final Window w = new PhoneWindow(mContext);
7 mWindow = w;
8 //设置回调
9 w.setCallback(this);
10 w.setOnWindowDismissedCallback(this);
11 w.setOnWindowSwipeDismissedCallback(() -> {
12 if (mCancelable) {
13 cancel();
14 }
15 });
16 w.setWindowManager(mWindowManager, null, null);
17 ...
18 }
接着看setContentView,和activity类似,把内容视图放入DecorView:
1public void setContentView(@LayoutRes int layoutResID) {
2 mWindow.setContentView(layoutResID);
3 }
再看下show方法:
1 public void show() {
2 ...
3 mDecor = mWindow.getDecorView();
4 ...
5 WindowManager.LayoutParams l = mWindow.getAttributes();
6 boolean restoreSoftInputMode = false;
7 if ((l.softInputMode
8 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
9 l.softInputMode |=
10 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
11 restoreSoftInputMode = true;
12 }
13 //使用WindowManager.addView
14 mWindowManager.addView(mDecor, l);
15 ...
16 }
注意,一般创建dialog时 传入的context必须是Activity。如果要传Application,那么要dialog.getWindow().setType(),设置系统window的type。
3.3 Toast的window创建
使用Toast方式:
1 Toast.makeText(this, "hehe", Toast.LENGTH_SHORT).show();
看makeText(),就是new一个Toast,设置mNextView为TextView、mDuration:
1 public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
2 return makeText(context, null, text, duration);
3 }
1 public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
2 @NonNull CharSequence text, @Duration int duration) {
3 //实例化
4 Toast result = new Toast(context, looper);
5
6 LayoutInflater inflate = (LayoutInflater)
7 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
8 View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
9 TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
10 tv.setText(text);
11 //设置视图、时间
12 result.mNextView = v;
13 result.mDuration = duration;
14
15 return result;
16 }
Toast构造方法:
1 public Toast(@NonNull Context context, @Nullable Looper looper) {
2 mContext = context;
3 //有个TN,是个Binder对象
4 mTN = new TN(context.getPackageName(), looper);
5 mTN.mY = context.getResources().getDimensionPixelSize(
6 com.android.internal.R.dimen.toast_y_offset);
7 mTN.mGravity = context.getResources().getInteger(
8 com.android.internal.R.integer.config_toastDefaultGravity);
9 }
实际也可以用setView()自定义视图:
1 public void setView(View view) {
2 mNextView = view;
3 }
再看show():
1 public void show() {
2 //没有视图不行
3 if (mNextView == null) {
4 throw new RuntimeException("setView must have been called");
5 }
6
7 INotificationManager service = getService();
8 String pkg = mContext.getOpPackageName();
9 TN tn = mTN;
10 tn.mNextView = mNextView;
11
12 try {
13 //IPC过程:NotificationManagerServcice.enqueueToast(),为啥要IPC过程呢?(注意这里的tn就是Toast构造方法里的new的TN)
14 service.enqueueToast(pkg, tn, mDuration);
15 } catch (RemoteException e) {
16 // Empty
17 }
18 }
看下NotificationManagerServcice.enqueueToast():
1//创建ToastRecord,callback就是传进来的TN
2record = new ToastRecord(callingPid, pkg, callback, duration, token);
3 mToastQueue.add(record);
4...
5if (index == 0) {
6 //这里看起来是show方法
7 showNextToastLocked();
8 }
看不showNextToastLocked():
1 void showNextToastLocked() {
2 //取出第一个record,这里为啥第0个?
3 ToastRecord record = mToastQueue.get(0);
4 while (record != null) {
5 if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
6 try {
7 //这里跑到TN的show方法了,显然是系统服务NotificationManagerServcice向我们的APP发起IPC过程,完成最终的show。这个保留疑问后面再看~
8 record.callback.show(record.token);
9 //这个就是 定时 调TN的hide方法,时间就是我们的toast的设置的show时间?为啥这么说,往下看~
10 scheduleDurationReachedLocked(record);
11 return;
12 }
13 ...
14 }
15 }
看下scheduleDurationReachedLocked(record):
1 private void scheduleDurationReachedLocked(ToastRecord r)
2 {
3 mHandler.removeCallbacksAndMessages(r);
4 Message m = Message.obtain(mHandler, MESSAGE_DURATION_REACHED, r);
5 long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
6 //handler发送定时任务MESSAGE_DURATION_REACHED,看名字就是隐藏toast,时间就是我们的long或者short
7 mHandler.sendMessageDelayed(m, delay);
8 }
这个mHandler就是NMS中的handler,找到上面任务的处理方法:
1 private void handleDurationReached(ToastRecord record)
2 {
3 synchronized (mToastQueue) {
4 int index = indexOfToastLocked(record.pkg, record.callback);
5 if (index >= 0) {
6 cancelToastLocked(index);
7 }
8 }
9 }
接着看:
1 void cancelToastLocked(int index) {
2 ToastRecord record = mToastQueue.get(index);
3 try {
4 //果然,是TN的hide方法,哈哈
5 record.callback.hide();
6 } catch (RemoteException e)
7 ...
8 ToastRecord lastToast = mToastQueue.remove(index);
9 if (mToastQueue.size() > 0) {
10 // 开始下一个~~~
11 showNextToastLocked();
12 }
13 }
总结下NotificationManagerServcice.enqueueToast()这个IPC的作用:使用NMS中的mHandler 处理队列中的ToastRecord,具体就是通过IPC调用Toast中的TN的show(),然后在定时调用TN的hide()。就是说,系统来保证toast的循序排队,及展示时间。
另外还一点,对非系统应用,队列中最多同时又50个ToastRecord:
1 // limit the number of outstanding notificationrecords an app can have
2 //MAX_PACKAGE_NOTIFICATIONS = 50
3 int count = getNotificationCountLocked(pkg, userId, id, tag);
4 if (count >= MAX_PACKAGE_NOTIFICATIONS) {
5 mUsageStats.registerOverCountQuota(pkg);
6 Slog.e(TAG, "Package has already posted or enqueued " + count
7 + " notifications. Not showing more. package=" + pkg);
8 return false;
9 }
好了,系统进程看完了。接着看实例化Toast时的创建的TN,我们在上面分析,猜测 这里才是我们想要的WIndow的创建过程,那么往下看吧:
1 private static class TN extends ITransientNotification.Stub {
2 private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
3
4 private static final int SHOW = 0;
5 private static final int HIDE = 1;
6 private static final int CANCEL = 2;
7 final Handler mHandler;
8 ...
9
10 static final long SHORT_DURATION_TIMEOUT = 4000;
11 static final long LONG_DURATION_TIMEOUT = 7000;
12
13 TN(String packageName, @Nullable Looper looper) {
14 final WindowManager.LayoutParams params = mParams;
15 ...
16 //window的type:TYPE_TOAST = FIRST_SYSTEM_WINDOW+5,是个系统window
17 params.type = WindowManager.LayoutParams.TYPE_TOAST;
18 params.setTitle("Toast");
19 //window的flags
20 params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
21 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
22 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
23
24 mPackageName = packageName;
25
26 //这里可知,必须在有looper的线程才能new Toast,为啥呢?因为前面分析NMS中调用TN的show、Hide,因为是IPC过程,实际在App这边执行是在Bind线程池中进行的,所以需要切换到当前发Toast的线程
27 if (looper == null) {
28 // Use Looper.myLooper() if looper is not specified.
29 looper = Looper.myLooper();
30 if (looper == null) {
31 throw new RuntimeException(
32 "Can't toast on a thread that has not called Looper.prepare()");
33 }
34 }
35 mHandler = new Handler(looper, null) {
36 @Override
37 public void handleMessage(Message msg) {
38 switch (msg.what) {
39 case SHOW: {
40 IBinder token = (IBinder) msg.obj;
41 handleShow(token);
42 break;
43 }
44 case HIDE: {
45 handleHide();
46 // Don't do this in handleHide() because it is also invoked by
47 // handleShow()
48 mNextView = null;
49 break;
50 }
51 case CANCEL: {
52 handleHide();
53 // Don't do this in handleHide() because it is also invoked by
54 // handleShow()
55 mNextView = null;
56 try {
57 getService().cancelToast(mPackageName, TN.this);
58 } catch (RemoteException e) {
59 }
60 break;
61 }
62 }
63 }
64 };
65 }
66
67 /**
68 * schedule handleShow into the right thread
69 */
70 @Override
71 public void show(IBinder windowToken) {
72 if (localLOGV) Log.v(TAG, "SHOW: " + this);
73 mHandler.obtainMessage(SHOW, windowToken).sendToTarget();
74 }
75
76 /**
77 * schedule handleHide into the right thread
78 */
79 @Override
80 public void hide() {
81 if (localLOGV) Log.v(TAG, "HIDE: " + this);
82 mHandler.obtainMessage(HIDE).sendToTarget();
83 }
84
85 public void cancel() {
86 if (localLOGV) Log.v(TAG, "CANCEL: " + this);
87 mHandler.obtainMessage(CANCEL).sendToTarget();
88 }
89
90 public void handleShow(IBinder windowToken) {
91 ...
92 if (mView != mNextView) {
93 // remove the old view if necessary
94 handleHide();
95 //mNextView赋值给mView
96 mView = mNextView;
97 ...
98 //1.获取WindowManager
99 mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
100 // the layout direction
101 final Configuration config = mView.getContext().getResources().getConfiguration();
102 final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
103 ...
104 ...
105 try {
106 //2.windowManager的addView
107 mWM.addView(mView, mParams);
108 trySendAccessibilityEvent();
109 } catch (WindowManager.BadTokenException e) {
110 /* ignore */
111 }
112 }
113 }
114
115 public void handleHide() {
116 if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
117 if (mView != null) {
118 if (mView.getParent() != null) {
119 if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
120 //windowManager的removeView
121 mWM.removeViewImmediate(mView);
122 }
123 ...
124 mView = null;
125 }
126 }
127 }
所以,TN才是Toast中真正处理Window创建的地方。
好了,Window讲完啦!
参考:
初步理解 Window 体系
Window, WindowManager和WindowManagerService之间的关系
(点击"阅读原文查看")